package etcdv3

import (
	"fmt"
	etcd3 "github.com/coreos/etcd/clientv3"
	"golang.org/x/net/context"
	"google.golang.org/grpc/naming"
	"github.com/coreos/etcd/mvcc/mvccpb"
)

// watcher is the implementaion of grpc.naming.Watcher
type watcher struct {
	re            *resolver // re: Etcd Resolver
	client        etcd3.Client
	isInitialized bool
}

// Close do nothing
func (w *watcher) Close() {
}

// Next to return the updates
func (w *watcher) Next() ([]*naming.Update, error) {
	// prefix is the etcd prefix/value to watch
	prefix := fmt.Sprintf("/%s/%s/", w.re.serviceNamePrefix, w.re.serviceName)

	// check if is initialized
	if !w.isInitialized {
		// query addresses from etcd
		return queryEtcdAddrs(w, prefix)
	} else {
		// generate etcd Watcher
		return watchEtcdAddrs(w, prefix)
	}
	return nil, nil
}

// query addresses from etcd
func queryEtcdAddrs(w *watcher, prefix string) ([]*naming.Update, error) {
	// query addresses from etcd
	resp, err := w.client.Get(context.Background(), prefix, etcd3.WithPrefix())
	w.isInitialized = true
	if err == nil {
		addrs := extractAddrs(resp)
		//if not empty, return the updates or watcher new dir
		if l := len(addrs); l != 0 {
			updates := make([]*naming.Update, l)
			for i := range addrs {
				updates[i] = &naming.Update{Op: naming.Add, Addr: addrs[i]}
			}
			return updates, nil
		}
	}
	return nil, nil
}

// extractAddrs
func extractAddrs(resp *etcd3.GetResponse) (addrs []string) {

	if resp == nil || resp.Kvs == nil {
		return addrs
	}

	for i := range resp.Kvs {
		if v := resp.Kvs[i].Value; v != nil {
			addrs = append(addrs, string(v))
		}
	}

	return addrs
}

// generate etcd Watcher
func watchEtcdAddrs(w *watcher, prefix string) ([]*naming.Update, error) {
	// generate etcd Watcher
	rch := w.client.Watch(context.Background(), prefix, etcd3.WithPrefix())
	for wresp := range rch {
		for _, ev := range wresp.Events {
			switch ev.Type {
			case mvccpb.PUT:
				return []*naming.Update{{Op: naming.Add, Addr: string(ev.Kv.Value)}}, nil
			case mvccpb.DELETE:
				return []*naming.Update{{Op: naming.Delete, Addr: string(ev.Kv.Value)}}, nil
			}
		}
	}
	return nil, nil
}
